#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Projet CellMates
# Judith ROBSON
# Roxane LEVY
# Ludovic JURADO


import sys
import copy
import mcm.Globals
import mcm.Widgets
import mcm.Options
import mcm.autoCell
import mcm.huffman
import mcm.transRegles
from PySide.QtCore import *
from PySide.QtGui import *
from bitarray import bitarray


""" Classe "MAIN".
	Elle crée l'application et gère les interactions entre l'utilisateur et l'application.

Lors de son initialisation, la fenêtre est créée, ainsi que le menu, l'automate et les
différentes options. Les valeurs de départ sont initialisées, puis les boutons sont connectés
à leur(s) fonction(s).

Dans cette classe, on trouve :
- une fonction d'actualisation pour chaque option
- une fonction de connexion qui associe, à chaque bouton, une ou plusieurs fonction(s)
- une fonction d'initialisation des variables globales
- une ou plusieurs fonction(s) exécutant l'action de chaque bouton
- plusieurs fonctions de traduction (entre seuil et densité, entre syntaxe interne et syntaxe externe des règles, entre configuration compressé et variables globales)
- une fonction d'actualisation de l'automate
- le moteur du mini-jeu
"""

class Main :

	# Fonction d'initialisation
	def __init__(self) :
		self.application = QApplication(sys.argv) # Création de l'application
		self.mainWindow = QMainWindow() # Création de la fenêtre principale
		self.mainWindow.setWindowTitle(qApp.trUtf8("CellMates")) # Titre de la fenêtre principale
		self.mainWindow.resize(800, 600)
		window = QWidget() # Création du widget central
		mainLayout = QHBoxLayout() # Création du layout principal

		menu = QMenuBar(self.mainWindow) # Création du menu
		fichier = QMenu(qApp.trUtf8("&Fichier")) # Menu
		self.new = fichier.addAction(qApp.trUtf8("&Nouveau"))
		self.save = fichier.addAction(qApp.trUtf8("&Sauvegarder"))
		self.load = fichier.addAction(qApp.trUtf8("&Charger"))
		self.quit = fichier.addAction(qApp.trUtf8("&Quitter"))
		menu.addMenu(fichier) # Attachement du premier menu
		self.mainWindow.setMenuBar(menu) # Attachement de la barre de menu à la fenêtre principale

		# Création de la grille
		self.grille = mcm.Widgets.Grille()
		grilleView = mcm.Widgets.GrilleView(self.grille)
		mainLayout.addWidget(grilleView)

		self.initGlobals() # Initialisation des variables globales

		self.numGlobals = 21

		self.autoCell = mcm.autoCell.AutoCell(mcm.Globals.grilleLignes, mcm.Globals.grilleColonnes,
				      					    mcm.Globals.grille, mcm.Globals.nombreEtats,
				      					    mcm.Globals.torus, mcm.Globals.tailleVoisinage,
				    					    mcm.Globals.regles, mcm.Globals.poidsVoisinage,
				   						    self.densiteToSeuil(mcm.Globals.densite)) # Création de l'automate

		self.transRegles = mcm.transRegles.TransRegles() # objet permettant traduction syntaxe interne vers utilisateur et vice versa
		self.huffMan = mcm.huffman.Huffman()   # objet permettant la compression et la décompression d'une partie de jeu
		# Création des options
		optionsArea = QScrollArea()
		optionsArea.setFixedWidth(630)
		self.options = mcm.Options.Options()
		self.resetOptions() # Initialisation des options
		optionsArea.setWidget(self.options)
		mainLayout.addWidget(optionsArea)

		self.timer = QTimer() # Création du timer permettant le défilement automatique
		self.timer.setInterval(5000/mcm.Globals.vitesse) # Initialisation de la vitesse de défilement

		self.modeArr = False

		self.connexions() # Connexion des boutons

		window.setLayout(mainLayout) # Attachement du layout principal au widget central
		self.mainWindow.setCentralWidget(window) # Attachement du widget central à la fenêtre principale

		self.mainWindow.show() # Affichage de l'application
		self.application.exec_() # Boucle principale


	# Fonction d'initialisation des variables globales
	def initGlobals(self) :
		mcm.Globals.statutJDLM = False
		mcm.Globals.pointsJDLM = 0
		mcm.Globals.pointsMaxJDLM = [0, 1]
		mcm.Globals.difficulteJDLM = 1
		mcm.Globals.limiteJDLM = 1000
		mcm.Globals.nombreEtats = 2
		mcm.Globals.colors = [QColor("white"), QColor("black"),  QColor("red"), QColor(0,255,0), QColor("blue")]
		mcm.Globals.selectedState = 0
		mcm.Globals.densite = 2 * [50] + 3 * [0]
		mcm.Globals.tailleVoisinage = 5 * [1]
		mcm.Globals.typeVoisinage = 5 * [1]
		mcm.Globals.totalistique = 5 * [True]
		mcm.Globals.poidsVoisinage = []
		for i in range(5) :
			mcm.Globals.poidsVoisinage.append([])
			taille = mcm.Globals.tailleVoisinage[i]
			diametre = (taille + 1) * 2 - 1
			for j in range(diametre) :
				mcm.Globals.poidsVoisinage[i].append([])
				for k in range(diametre) :
					if j == taille and k == taille :
						mcm.Globals.poidsVoisinage[i][j].append(0)
					else :
						mcm.Globals.poidsVoisinage[i][j].append(1)
		mcm.Globals.torus = False
		mcm.Globals.grilleLignes = 20
		mcm.Globals.grilleColonnes = 20
		mcm.Globals.regles = {0 : {1 : {1 : [3]}}, 1 : {1 : {1 : [2, 3]}}}
		mcm.Globals.vitesse = 5
		mcm.Globals.etape = 0
		self.grille.initGrille()


	# fonction mettant à jour les options du "Jeu de la mort"
	def resetJDLM(self) :
		self.options.valDifficulte.setValue(mcm.Globals.difficulteJDLM)
		self.options.valLimEtape.setValue(mcm.Globals.limiteJDLM)
		self.options.scoreJDLM.setText(str(int(mcm.Globals.pointsMaxJDLM[0])) + " (" + str(mcm.Globals.pointsMaxJDLM[1]) + ") " + " / -")


	# Fonction mettant à jour le nombre d'états sélectionnés par l'utilisateur
	def resetNombreEtats(self) :
		for i in range(4) :
			if i == mcm.Globals.nombreEtats - 2 :
				self.options.etats[i].setChecked(True)
			else :
				self.options.etats[i].setChecked(False)

		for i in range(mcm.Globals.nombreEtats) :
			self.options.couleur[i].setEnabled(True)
		for i in range(mcm.Globals.nombreEtats, 5) :
			self.options.couleur[i].setEnabled(False)


	# Fonction mettant à jour les couleurs correspondant aux états
	def resetCouleurs(self) :
		for i in range(5) :
			self.options.couleur[i].setColor(mcm.Globals.colors[i])


	# Fonction mettant à jour l'état sélectionné
	def resetSelectedColor(self) :
		for i in range(5) :
			if i == mcm.Globals.selectedState :
				self.options.couleur[i].setSize(QSize(30, 30))
			else :
				self.options.couleur[i].setSize(QSize(20, 20))


	# Fonction ré-initialisant les densités de couleur (à un pourcentage équitable)
	def resetSeuils(self) :
		self.options.eqDensite.setChecked(False)

		seuil = 100 // mcm.Globals.nombreEtats
		gap = 100 % mcm.Globals.nombreEtats

		for i in range(5) :
			self.options.densites[i].setValue(0)

		self.options.densites[0].setValue(seuil+gap)

		for i in range(1, mcm.Globals.nombreEtats) :
			self.options.densites[i].setValue(seuil)

		self.setMaxDensite()


	# Fonction mettant à jour la taille du voisinage
	def resetTailleVoisinage(self) :
		for i in range(3) :
			if i == mcm.Globals.tailleVoisinage[mcm.Globals.selectedState] - 1 :
				self.options.taille[i].setChecked(True)
			else :
				self.options.taille[i].setChecked(False)


	# Fonction mettant à jour le voisinage
	def resetVoisinage(self) :
		if mcm.Globals.typeVoisinage[mcm.Globals.selectedState] == 1 :
			self.options.moore.setChecked(True)
			self.options.vonNeumann.setChecked(False)
			self.options.voisinagePerso.setChecked(False)
		elif mcm.Globals.typeVoisinage[mcm.Globals.selectedState] == 2 :
			self.options.moore.setChecked(False)
			self.options.vonNeumann.setChecked(True)
			self.options.voisinagePerso.setChecked(False)
		else :
			self.options.moore.setChecked(False)
			self.options.vonNeumann.setChecked(False)
			self.options.voisinagePerso.setChecked(True)

		self.options.nonTotalistique.setChecked(not mcm.Globals.totalistique[mcm.Globals.selectedState])

		if mcm.Globals.typeVoisinage[mcm.Globals.selectedState] :
			self.options.aleatoire.setEnabled(False)
			self.options.nonTotalistique.setEnabled(False)
			poids = self.autoCell.genPoids(mcm.Globals.typeVoisinage[mcm.Globals.selectedState], True, mcm.Globals.tailleVoisinage[mcm.Globals.selectedState])
			mcm.Globals.poidsVoisinage[mcm.Globals.selectedState] = poids
		else :
			self.options.aleatoire.setEnabled(True)
			self.options.nonTotalistique.setEnabled(True)

		self.options.voisinageConfig.update()


	# Fonction mettant à jour le type de grille
	def resetTypeGrille(self) :
		self.options.toroidale.setChecked(mcm.Globals.torus)
		self.options.bornee.setChecked(not mcm.Globals.torus)


	# Fonction mettant à jour la taille de la grille
	def resetTailleGrille(self) :
		self.options.ligne.setValue(mcm.Globals.grilleLignes)
		self.options.colonne.setValue(mcm.Globals.grilleColonnes)


	# Fonction mettant à jour l'affichage des règles
	def resetRegles(self) :
		self.options.listeRegles.setText(qApp.trUtf8(self.reglesIntToExt(mcm.Globals.regles)))


	# Fonction mettant à jour la vitesse de défilement
	def resetVitesse(self) :
		self.options.vitesseSlider.setValue(mcm.Globals.vitesse)


	# Fonction ré-initialisant l'étape courante
	def resetEtape(self) :
		mcm.Globals.etape = 0
		self.options.numEtape.setValue(0)


	# Fonction remettant toutes les options à jour
	def resetOptions(self) :
		self.resetJDLM()
		self.resetNombreEtats()
		self.resetCouleurs()
		self.resetSelectedColor()
		self.resetSeuils()
		self.resetTypeGrille()
		self.resetTailleGrille()
		self.resetTailleVoisinage()
		self.resetVoisinage()
		self.resetRegles()
		self.resetVitesse()
		self.resetEtape()


	# Fonction ré-initialisant toutes les options
	def resetAll(self) :
		self.initGlobals()
		self.resetOptions()


	# affiche un message d'erreur dans un QMessageBox
	def afficheErreur(self, message) :
		error = QMessageBox()
		error.setInformativeText(qApp.trUtf8(message))
		error.setWindowTitle(qApp.trUtf8("Erreur"))
		spacer = QSpacerItem(300, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
		layout = error.layout()
		layout.addItem(spacer, layout.rowCount(), 0, 1, layout.columnCount())
		error.exec_()


	# sauvegarde l'automate et la configuration courante dans un fichier après compression
	def saveAutomate(self) :
		# ouvre une boîte de dialogue avec le répertoire courant
		filename = QFileDialog.getSaveFileName(self.mainWindow, "Sauvegarde de la configuration")
		# récupère toutes les globales et les met dans une liste
		liste = []
		liste.append(mcm.Globals.statutJDLM)
		liste.append(mcm.Globals.pointsJDLM)
		liste.append(mcm.Globals.pointsMaxJDLM)
		liste.append(mcm.Globals.difficulteJDLM)
		liste.append(mcm.Globals.limiteJDLM)
		liste.append(mcm.Globals.nombreEtats)
		liste.append([])
		for x in range(len(mcm.Globals.colors)) :
			liste[len(liste) - 1].append([mcm.Globals.colors[x].red(), mcm.Globals.colors[x].green(), mcm.Globals.colors[x].blue()])
		liste.append(mcm.Globals.selectedState)
		liste.append(mcm.Globals.densite)
		liste.append(mcm.Globals.tailleVoisinage)
		liste.append(mcm.Globals.typeVoisinage)
		liste.append(mcm.Globals.totalistique)
		liste.append(mcm.Globals.poidsVoisinage)
		liste.append(mcm.Globals.torus)
		liste.append(mcm.Globals.grilleLignes)
		liste.append(mcm.Globals.grilleColonnes)
		liste.append(mcm.Globals.regles)
		liste.append(mcm.Globals.vitesse)
		liste.append(mcm.Globals.etape)
		liste.append(mcm.Globals.grille)
		liste.append(mcm.Globals.grillesAnte)

		if filename :
			if filename == (u'', u'') :   # si l'utilisateur décide d'annuler la sauvegarde
				return
			try : fichier = open((filename[0]+'.cmc' if (filename[0][-4:] != ".cmc") else filename[0]), 'w')
			except :
				self.afficheErreur("Erreur à l'ouverture du fichier " + filename[0])
				return
			self.huffMan.__init__()  # initialisation
			self.huffMan.matrice = str(liste)
			self.huffMan.encode()
			fichier.write(repr(self.huffMan.prefix)+'\n')
			fichier.write(str(self.huffMan.huff.buffer_info()[3]))
			self.huffMan.huff.tofile(fichier)
			fichier.close()
			QMessageBox.information(self.mainWindow, self.mainWindow.trUtf8("Sauvegarde"), self.mainWindow.trUtf8("La sauvegarde est terminée"))


	# vérifie et extrait les données issues de la décompression
	def extractMatrice(self, matrice) :
		if len(matrice) is not self.numGlobals : return False

		# pour chaque entrée on vérifie qu'elle correspond au type et aux valeurs qu'on attend pour les globales

		if type(matrice[0]) is bool : mcm.Globals.statutJDLM = matrice[0]
		else : self.resetAll(); return False

		if (type(matrice[1]) is int or type(matrice[1]) is float) and matrice[1] >= 0 : mcm.Globals.pointsJDLM = matrice[1]
		else : self.resetAll(); return False

		if type(matrice[2]) is list and len(matrice[2]) is 2 and (type(matrice[2][0]) is int or type(matrice[2][0]) is float) and matrice[2][0] >= 0 and matrice[2][1] in range(1, 11) :
			mcm.Globals.pointsMaxJDLM = matrice[2]
		else : self.resetAll(); return False

		if matrice[3] in range(1, 11) : mcm.Globals.difficulteJDLM = matrice[3]
		else : self.resetAll(); return False

		if matrice[4] in range(100, 10001) : mcm.Globals.limiteJDLM = matrice[4]
		else : self.resetAll(); return False

		if matrice[5] in range(2, 6) : mcm.Globals.nombreEtats = matrice[5]
		else : self.resetAll(); return False

		if type(matrice[6]) is list and len(matrice[6]) is 5 :
			valide = True
			couleurs = []
			for i in range(5) :
				if type(matrice[6][i]) is list and len(matrice[6][i]) is 3 :
					for j in range(3) :
						if matrice[6][i][j] not in range(0, 256) : valide = False
				else :
					valide = False
				if valide : couleurs.append(QColor(matrice[6][i][0], matrice[6][i][1], matrice[6][i][2]))
			if valide : mcm.Globals.colors = couleurs
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if matrice[7] in range(0, 5) : mcm.Globals.selectedState = matrice[7]
		else : self.resetAll(); return False

		if type(matrice[8]) is list and len(matrice[8]) is 5 :
			valide = True
			for i in range(5) :
				if matrice[8][i] not in range(0, 101) : valide = False
			if valide : mcm.Globals.densite = matrice[8]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[9]) is list and len(matrice[9]) is 5 :
			valide = True
			for i in range(5) :
				if matrice[9][i] not in range(1, 4) : valide = False
			if valide : mcm.Globals.tailleVoisinage = matrice[9]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[10]) is list and len(matrice[10]) is 5 :
			valide = True
			for i in range(5) :
				if matrice[10][i] not in range(0, 3) : valide = False
			if valide : mcm.Globals.typeVoisinage = matrice[10]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[11]) is list and len(matrice[11]) is 5 :
			valide = True
			for i in range(5) :
				if type(matrice[11][i]) is not bool : valide = False
			if valide : mcm.Globals.totalistique = matrice[11]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[12]) is list and len(matrice[12]) is 5 :
			valide = True
			for i in range(5) :
				diametre = (mcm.Globals.tailleVoisinage[i] + 1) * 2 - 1
				if type(matrice[12][i]) is list and len(matrice[12][i]) is diametre :
					for j in range(diametre) :
						if type(matrice[12][i][j]) is list and len(matrice[12][i][j]) is diametre :
							for k in range(diametre) :
								if type(matrice[12][i][j][k]) is not int or matrice[12][i][j][k] < 0 : valide = False
						else : valide = False
				else : valide = False
			if valide : mcm.Globals.poidsVoisinage = matrice[12]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[13]) is bool : mcm.Globals.torus = matrice[13]
		else : self.resetAll(); return False

		if matrice[14] in range(2, 101) : mcm.Globals.grilleLignes = matrice[14]
		else : self.resetAll(); return False

		if matrice[15] in range(2, 101) : mcm.Globals.grilleColonnes = matrice[15]
		else : self.resetAll(); return False

		if type(matrice[16]) is dict and len(matrice[16]) is mcm.Globals.nombreEtats :
			valide = True
			for cleP1, valeurP1 in matrice[16].items() :
				if cleP1 in range(0, mcm.Globals.nombreEtats) and type(valeurP1) is dict and len(valeurP1) is mcm.Globals.nombreEtats - 1 :
					maxVal = pow((mcm.Globals.tailleVoisinage[cleP1] + 1) * 2 - 1, 2) + 1
					for cleP2, valeurP2 in valeurP1.items() :
						if cleP2 in range(1, mcm.Globals.nombreEtats) and type(valeurP2) is dict and len(valeurP2) is mcm.Globals.nombreEtats - 1 :
							for cleP3, valeurP3 in valeurP2.items() :
								if cleP3 in range(1, mcm.Globals.nombreEtats) and type(valeurP3) is list :
									for val in valeurP3 :
										if val not in range(0, maxVal) : valide = False
								else : valide = False
						else : valide = False
				else : valide = False
			if valide : mcm.Globals.regles = matrice[16]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if matrice[17] in range(1, 31) : mcm.Globals.vitesse = matrice[17]
		else : self.resetAll(); return False

		if matrice[18] in range(0, 10001) :
			mcm.Globals.etape = matrice[18]
			self.options.numEtape.setValue(mcm.Globals.etape)
		else : self.resetAll(); return False

		if type(matrice[19]) is list and len(matrice[19]) is mcm.Globals.grilleLignes :
			valide = True
			for i in range(mcm.Globals.grilleLignes) :
				if type(matrice[19][i]) is list and len(matrice[19][i]) is mcm.Globals.grilleColonnes :
					for j in range(mcm.Globals.grilleColonnes) :
						if matrice[19][i][j] not in range(0, mcm.Globals.nombreEtats) : valide = False
				else : valide = False
			if valide : mcm.Globals.grille = matrice[19]
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		if type(matrice[20]) is list and len(matrice[20]) > 0 :
			# on suppose que si la première est valide les suivantes aussi (gain de temps)
			if type(matrice[20][0]) is list and len(matrice[20][0]) is mcm.Globals.grilleLignes :
				valide = True
				for i in range(mcm.Globals.grilleLignes) :
					if type(matrice[20][0][i]) is list and len(matrice[20][0][i]) is mcm.Globals.grilleColonnes :
						for j in range(mcm.Globals.grilleColonnes) :
							if matrice[20][0][i][j] not in range(0, mcm.Globals.nombreEtats) : valide = False
					else : valide = False
				if valide : mcm.Globals.grillesAnte = matrice[20]
				else : self.resetAll(); return False
			else : self.resetAll(); return False
		else : self.resetAll(); return False

		# on met à jour les options, la grille et l'automate

		self.resetJDLM()
		self.resetNombreEtats()
		self.resetCouleurs()
		self.resetSelectedColor()
		for i in range(5) :
			self.options.densites[i].setValue(mcm.Globals.densite[i])
		self.resetTypeGrille()
		self.resetTailleGrille()
		self.resetTailleVoisinage()
		self.resetVoisinage()
		self.resetRegles()
		self.resetVitesse()

		self.grille.update()
		self.autoCellUpdate()

		return True


	# charge un fichier contenant un automate et une configuration, le décompresse, et met à jour les données du programme
	def loadAutomate(self) :
		filename = QFileDialog.getOpenFileName(self.mainWindow, "Chargement de la configuration")
		if filename :
			if filename == (u'', u'') :   # si l'utilisateur décide d'annuler le chargement d'un fichier
				return
			if (filename[0][-4:] != ".cmc" ) :
				self.afficheErreur("Fichier non conforme")
				return
			try : fichier = open(filename[0], "r")
			except :
				self.afficheErreur("Erreur à l'ouverture du fichier " + filename[0])
				return
			self.huffMan.__init__()  # initialisation
			try : self.huffMan.prefix = eval(fichier.readline())   # récupère bien un codage préfixé
			except :
				self.afficheErreur("Fichier non conforme")
				fichier.close()
				return
			try : unused_bytes = int(fichier.read(1))      # récupère bien le nombre de unused_bytes
			except :
				self.afficheErreur("Fichier non conforme")
				fichier.close()
				return
			try : self.huffMan.huff.fromfile(fichier)  # récupère un bitarray()
			except :
				self.afficheErreur("Fichier non conforme")
				fichier.close()
				return
			fichier.close()
			if unused_bytes : del self.huffMan.huff[-unused_bytes:]
			self.huffMan.decode()
			try : m = eval(self.huffMan.matrice) # remet toutes les globales dans la matrice
			except :
				self.afficheErreur("Fichier non conforme")
				return
			if self.extractMatrice(m) :
				QMessageBox.information(self.mainWindow, self.mainWindow.trUtf8("Chargement"), self.mainWindow.trUtf8("Le chargement est terminé"))
			else : self.afficheErreur("Fichier non conforme")


	# Fonction connectant les boutons à leur(s) fonction(s)
	def connexions(self) :
		self.new.triggered.connect(lambda: self.resetAll())
		self.quit.triggered.connect(lambda: self.application.quit())
		self.save.triggered.connect(lambda: self.saveAutomate())
		self.load.triggered.connect(lambda: self.loadAutomate())

		self.options.activeJDLM.clicked.connect(lambda: self.alterneJDLM())
		self.options.valDifficulte.valueChanged.connect(lambda: self.changeDiffJDLM())
		self.options.valLimEtape.valueChanged.connect(lambda: self.changeLimiteJDLM())

		self.options.etats[0].pressed.connect(lambda: self.numberEtats(2))
		self.options.etats[1].pressed.connect(lambda: self.numberEtats(3))
		self.options.etats[2].pressed.connect(lambda: self.numberEtats(4))
		self.options.etats[3].pressed.connect(lambda: self.numberEtats(5))

		self.options.couleur[0].doubleClicked.connect(lambda: self.colorPick(0))
		self.options.couleur[0].clicked.connect(lambda: self.selectColor(0))
		self.options.couleur[1].doubleClicked.connect(lambda: self.colorPick(1))
		self.options.couleur[1].clicked.connect(lambda: self.selectColor(1))
		self.options.couleur[2].doubleClicked.connect(lambda: self.colorPick(2))
		self.options.couleur[2].clicked.connect(lambda: self.selectColor(2))
		self.options.couleur[3].doubleClicked.connect(lambda: self.colorPick(3))
		self.options.couleur[3].clicked.connect(lambda: self.selectColor(3))
		self.options.couleur[4].doubleClicked.connect(lambda: self.colorPick(4))
		self.options.couleur[4].clicked.connect(lambda: self.selectColor(4))

		self.options.densites[0].valueChanged.connect(lambda: self.setDensite(self.options.densites[0].value(), 0))
		self.options.densites[1].valueChanged.connect(lambda: self.setDensite(self.options.densites[1].value(), 1))
		self.options.densites[2].valueChanged.connect(lambda: self.setDensite(self.options.densites[2].value(), 2))
		self.options.densites[3].valueChanged.connect(lambda: self.setDensite(self.options.densites[3].value(), 3))
		self.options.densites[4].valueChanged.connect(lambda: self.setDensite(self.options.densites[4].value(), 4))
		self.options.newDensite.clicked.connect(lambda: self.genDensite())
		self.options.eqDensite.clicked.connect(lambda: self.genEqDensite())

		self.options.taille[0].pressed.connect(lambda: self.setTailleVois(1))
		self.options.taille[1].pressed.connect(lambda: self.setTailleVois(2))
		self.options.taille[2].pressed.connect(lambda: self.setTailleVois(3))

		self.options.moore.pressed.connect(lambda: self.setTypeVoisinage(1))
		self.options.vonNeumann.pressed.connect(lambda: self.setTypeVoisinage(2))
		self.options.voisinagePerso.pressed.connect(lambda: self.setTypeVoisinage(0))
		self.options.aleatoire.clicked.connect(lambda: self.genRandomVois())
		self.options.nonTotalistique.stateChanged.connect(lambda: self.setTotalistique())

		self.options.bornee.pressed.connect(lambda: self.setType(False))
		self.options.toroidale.pressed.connect(lambda: self.setType(True))

		self.options.ligne.editingFinished.connect(lambda: self.setLigne())
		self.options.colonne.editingFinished.connect(lambda: self.setColonne())

		self.options.mono.clicked.connect(lambda: self.genMonocolore())
		self.options.random.clicked.connect(lambda: self.genRandomGrille())

		self.options.randomRegles.clicked.connect(lambda: self.genRandomRegles())
		self.options.persoRegles.clicked.connect(lambda: self.openChoixRegles())

		self.options.vitesseSlider.valueChanged.connect(lambda: self.setVitesse())

		self.options.numEtape.valueChanged.connect(lambda: self.etapeN())
		self.options.playArriere.clicked.connect(lambda: self.playArriere())
		self.options.playAvant.clicked.connect(lambda: self.playAvant())
		self.options.pause.clicked.connect(lambda: self.pause())

		self.options.gen.clicked.connect(lambda: self.genRandom())

		self.timer.timeout.connect(lambda: self.etapeGrille())


	# fonction qui active/désactive les options
	def alterneOptions(self, enable) :
		self.options.valDifficulte.setEnabled(enable)
		self.options.valLimEtape.setEnabled(enable)

		self.options.etats[0].setEnabled(enable)
		self.options.etats[1].setEnabled(enable)
		self.options.etats[2].setEnabled(enable)
		self.options.etats[3].setEnabled(enable)

		self.options.couleur[0].setEnabled(enable)
		self.options.couleur[1].setEnabled(enable)
		self.options.couleur[2].setEnabled(enable)
		self.options.couleur[3].setEnabled(enable)
		self.options.couleur[4].setEnabled(enable)

		self.options.densites[0].setEnabled(enable)
		self.options.densites[1].setEnabled(enable)
		self.options.densites[2].setEnabled(enable)
		self.options.densites[3].setEnabled(enable)
		self.options.densites[4].setEnabled(enable)
		self.options.newDensite.setEnabled(enable)
		self.options.eqDensite.setEnabled(enable)

		self.options.taille[0].setEnabled(enable)
		self.options.taille[1].setEnabled(enable)
		self.options.taille[2].setEnabled(enable)

		self.options.moore.setEnabled(enable)
		self.options.vonNeumann.setEnabled(enable)
		self.options.voisinagePerso.setEnabled(enable)
		self.options.aleatoire.setEnabled(enable)
		self.options.nonTotalistique.setEnabled(enable)

		self.options.bornee.setEnabled(enable)
		self.options.toroidale.setEnabled(enable)

		self.options.ligne.setEnabled(enable)
		self.options.colonne.setEnabled(enable)

		self.options.mono.setEnabled(enable)
		self.options.random.setEnabled(enable)

		self.options.randomRegles.setEnabled(enable)
		self.options.persoRegles.setEnabled(enable)

		self.options.vitesseSlider.setEnabled(enable)

		self.options.numEtape.setEnabled(enable)
		self.options.playArriere.setEnabled(enable)
		self.options.playAvant.setEnabled(enable)
		self.options.pause.setEnabled(enable)

		self.options.gen.setEnabled(enable)


	# fonction qui active/désactive le mode "Jeu de la Mort", et rend indisponibles les autres fonctionnalités du programme
	def alterneJDLM(self) :
		if self.options.activeJDLM.isChecked() :
			mcm.Globals.nombreEtats = 2
			mcm.Globals.densite = 2 * [50] + 3 * [0]
			mcm.Globals.tailleVoisinage = 5 * [1]
			mcm.Globals.typeVoisinage = 5 * [1]
			mcm.Globals.totalistique = 5 * [True]
			mcm.Globals.poidsVoisinage = []
			for i in range(5) :
				mcm.Globals.poidsVoisinage.append([])
				taille = mcm.Globals.tailleVoisinage[i]
				diametre = (taille + 1) * 2 - 1
				for j in range(diametre) :
					mcm.Globals.poidsVoisinage[i].append([])
					for k in range(diametre) :
						if j == taille and k == taille :
							mcm.Globals.poidsVoisinage[i][j].append(0)
						else :
							mcm.Globals.poidsVoisinage[i][j].append(1)
			mcm.Globals.regles = {0 : {1 : {1 : [3]}}, 1 : {1 : {1 : [2, 3]}}}
			self.resetNombreEtats()
			self.resetCouleurs()
			self.resetSeuils()
			self.resetTailleVoisinage()
			self.resetVoisinage()
			self.resetRegles()
			self.alterneOptions(False)
			self.genRandomGrille()
			self.selectColor(1)
			mcm.Globals.pointsJDLM = 0
			mcm.Globals.statutJDLM = True
			self.options.scoreJDLM.setText(str(int(mcm.Globals.pointsMaxJDLM[0])) + " (" + str(mcm.Globals.pointsMaxJDLM[1]) + ") " + " / " + str(int(mcm.Globals.pointsJDLM)))
			self.options.vitesseSlider.setValue(self.options.vitesseSlider.maximum())
			self.setVitesse()
			self.playAvant()
		else :
			self.pause()
			mcm.Globals.statutJDLM = False
			self.alterneOptions(True)
			self.options.scoreJDLM.setText(str(int(mcm.Globals.pointsMaxJDLM[0])) + " (" + str(mcm.Globals.pointsMaxJDLM[1]) + ") " + " / -")
			self.resetNombreEtats()
			self.resetCouleurs()
			self.selectColor(0)
			self.genMonocolore()

	# change la difficulté du "Jeu de la Mort"
	def changeDiffJDLM(self) : mcm.Globals.difficulteJDLM = self.options.valDifficulte.value()


	# change la limite du nombre d'étapes avant la fin de la partie pour le "Jeu de la Mort"
	def changeLimiteJDLM(self) : mcm.Globals.limiteJDLM = self.options.valLimEtape.value()


	# Fonction de sélection du nombre d'états
	def numberEtats(self, nb) :
		mcm.Globals.nombreEtats = nb
		mcm.Globals.selectedState = 0
		mcm.Globals.regles = False
		self.resetNombreEtats()
		self.resetCouleurs()
		self.resetSelectedColor()
		self.resetSeuils()
		self.resetRegles()
		self.genMonocolore()
		self.autoCellUpdate()
		self.setMaxDensite()


	# Fonction de sélection d'un état/couleur
	def selectColor(self, etat) :
		mcm.Globals.selectedState = etat
		self.resetSelectedColor()
		self.resetTailleVoisinage()
		self.resetVoisinage()


	# Fonction de personnalisation de couleur
	def colorPick(self, etat) :
		if self.options.couleur[etat].isEnabled() :
			color = QColorDialog.getColor(self.options.couleur[etat].getColor())
			if color.isValid() :
				mcm.Globals.colors[mcm.Globals.selectedState] = color
				self.resetCouleurs()
				self.grille.update()


	# Fonction de sélection des densités de couleur sur la grille
	def setDensite(self, densite, etat) :
		mcm.Globals.densite[etat] = densite
		self.setMaxDensite()


	# Fonction mettant à jour les maximums de densités dynamiquement (afin de toujours avoir un total de 100% maximum)
	def setMaxDensite(self) :
		nb = mcm.Globals.nombreEtats
		d1 = self.options.densites[0].value()
		d2 = self.options.densites[1].value()
		d3 = self.options.densites[2].value()
		d4 = self.options.densites[3].value()
		d5 = self.options.densites[4].value()

		self.options.densites[0].setMaximum(100 - d2 - d3 - d4 - d5)
		self.options.densites[1].setMaximum(100 - d1 - d3 - d4 - d5)

		if nb >= 3 :
			self.options.densites[2].setMaximum(100 - d1 - d2 - d4 - d5)
		else :
			self.options.densites[2].setMaximum(0)
		if nb >= 4 :
			self.options.densites[3].setMaximum(100 - d1 - d2 - d3 - d5)
		else :
			self.options.densites[3].setMaximum(0)
		if nb == 5 :
			self.options.densites[4].setMaximum(100 - d1 - d2 - d3 - d4)
		else :
			self.options.densites[4].setMaximum(0)


	# Fonction générant des densités (aléatoires si option d'équilibre non cochée)
	def genDensite(self) :
		self.autoCellUpdate()
		nb = mcm.Globals.nombreEtats
		seuils = self.autoCell.genSeuils(self.options.eqDensite.isChecked())
		densites = self.seuilToDensite(seuils)

		for i in range(5) :
			self.options.densites[i].setValue(0)
		for i in range(5) :
			self.options.densites[i].setValue(densites[i])

	# Fonction enclenchant la génération de densités équilibrées
	def genEqDensite(self) :
		if not self.options.eqDensite.isChecked() : return
		self.genDensite()

	# Fonction de sélection de la taille du voisinage (pour l'état courant)
	def setTailleVois(self, taille) :
		etat = mcm.Globals.selectedState
		poids = self.autoCell.genPoids(1, True, taille)

		mcm.Globals.totalistique[etat] = True
		mcm.Globals.poidsVoisinage[etat] = poids
		mcm.Globals.typeVoisinage[etat] = 1
		mcm.Globals.tailleVoisinage[etat] = taille
		self.resetTailleVoisinage()
		self.resetVoisinage()


	# Fonction de sélection du type de voisinage (pour l'état courant)
	def setTypeVoisinage(self, typeVois) :
		mcm.Globals.typeVoisinage[mcm.Globals.selectedState] = typeVois
		self.resetVoisinage()


	# Fonction générant un voisinage aléatoire (pour l'état courant)
	def genRandomVois(self) :
		etat = mcm.Globals.selectedState
		poids = self.autoCell.genPoids(0, mcm.Globals.totalistique[etat], mcm.Globals.tailleVoisinage[etat])
		mcm.Globals.poidsVoisinage[etat] = poids
		self.resetVoisinage()


	# Fonction permettant de choisir un voisinage non totalistique (pour l'état courant)
	def setTotalistique(self) :
		totalistique = not self.options.nonTotalistique.isChecked()
		mcm.Globals.totalistique[mcm.Globals.selectedState] = totalistique
		self.resetVoisinage()


	# Fonction de sélection du type de grille
	def setType(self, typeGrille) :
		mcm.Globals.torus = typeGrille


	# Fonction de sélection de la hauteur de la grille
	def setLigne(self) :
		self.pause()
		mcm.Globals.grilleLignes = self.options.ligne.value()
		self.genMonocolore()


	# Fonction de sélection de la largeur de la grille
	def setColonne(self) :
		self.pause()
		mcm.Globals.grilleColonnes = self.options.colonne.value()
		self.genMonocolore()


	# Fonction générant une grille monocolore (pour la couleur sélectionnée)
	def genMonocolore(self) :
		self.resetEtape()
		self.grille.initGrille()


	# Fonction générant une grille aléatoire
	def genRandomGrille(self) :
		self.resetEtape()
		self.autoCellUpdate()
		mcm.Globals.grille = self.autoCell.genGrille()
		mcm.Globals.grillesAnte = [copy.deepcopy(mcm.Globals.grille)]
		self.grille.update()


	# Fonction générant des règles aléatoires
	def genRandomRegles(self) :
		self.autoCellUpdate()
		regles = self.autoCell.genRegles()
		mcm.Globals.regles = regles
		self.resetRegles()


	# Fonction permettant de créer ses propres règles
	def openChoixRegles(self) :
		popup = mcm.Widgets.ReglesPopup()
		popup.setWindowTitle(qApp.trUtf8("Règles"))
		res = popup.exec_()
		if res :
			regles = self.reglesExtToInt(popup.texteRegles.toPlainText())
			mcm.Globals.regles = regles
			self.resetRegles()


	# Fonction de sélection de la vitesse de défilement
	def setVitesse(self) :
		mcm.Globals.vitesse = self.options.vitesseSlider.value()
		self.timer.setInterval(5000/mcm.Globals.vitesse)


	# Fonction permettant le défilement automatique vers l'arrière
	def playArriere(self) :
		self.modeArr = True
		self.timer.start()


	# Fonction permettant le défilement automatique vers l'avant
	def playAvant(self) :
		self.modeArr = False
		self.timer.start()


	# Fonction mettant en pause le défilement automatique
	def pause(self) :
		self.timer.stop()


	# fonction qui calcul le nombre de points gagnés à chaque étape (mode "Jeu de la Mort") et termine le jeu si fin de partie
	def calculPoints(self) :
		points = 0
		nbEnVie = 0
		for x in range(mcm.Globals.grilleLignes) :
			for y in range(mcm.Globals.grilleColonnes) :
				if mcm.Globals.grille[x][y] : nbEnVie += 1
				if mcm.Globals.grille[x][y] > mcm.Globals.grillesAnte[-2][x][y] : # si une cellule est venue à la vie
					# on tient compte de la difficulté comme de la taille de la grille
					points += (0.1 / mcm.Globals.difficulteJDLM) / ((mcm.Globals.grilleLignes * mcm.Globals.grilleColonnes) / (20.0 * 20.0))
		mcm.Globals.pointsJDLM += points
		if mcm.Globals.pointsJDLM > mcm.Globals.pointsMaxJDLM[0] :
			mcm.Globals.pointsMaxJDLM[0] = mcm.Globals.pointsJDLM
			mcm.Globals.pointsMaxJDLM[1] = mcm.Globals.difficulteJDLM
		if not nbEnVie or mcm.Globals.etape == mcm.Globals.limiteJDLM : # toutes les cellules mortes ou nombre d'étapes limite dépassé, fin de partie
			self.options.activeJDLM.setChecked(False)
			self.alterneJDLM()
		else :
			self.options.scoreJDLM.setText(str(int(mcm.Globals.pointsMaxJDLM[0])) + " (" + str(mcm.Globals.pointsMaxJDLM[1]) + ") " + " / " + str(int(mcm.Globals.pointsJDLM)))


	# Fonction permettant d'avancer d'une étape, vers l'avant ou vers l'arrière
	def etapeGrille(self) :
		if self.modeArr :
			if not mcm.Globals.etape : return
			mcm.Globals.grillesAnte.pop()
			self.autoCell.grille = copy.deepcopy(mcm.Globals.grillesAnte[-1])
			mcm.Globals.grille = self.autoCell.grille
			mcm.Globals.etape -= 1
		else :
			self.autoCellUpdate()
			self.autoCell.etape()
			mcm.Globals.grille = self.autoCell.grille
			mcm.Globals.grillesAnte.append(copy.deepcopy(mcm.Globals.grille))
			mcm.Globals.etape += 1
		self.grille.update()
		self.options.numEtape.setValue(mcm.Globals.etape)
		if mcm.Globals.statutJDLM :
			self.calculPoints()


	# Fonction permettant de passer à l'étape n
	def etapeN(self) :
		diff = self.options.numEtape.value() - mcm.Globals.etape

		if diff > 0 :
			self.autoCellUpdate()
			for i in range(diff) :
				self.autoCell.etape()
				mcm.Globals.grillesAnte.append(copy.deepcopy(self.autoCell.grille))
			mcm.Globals.grille = self.autoCell.grille
			self.grille.update()
			mcm.Globals.etape = self.options.numEtape.value()
		elif diff < 0 :
			mcm.Globals.grillesAnte = mcm.Globals.grillesAnte[:diff]
			self.autoCell.grille = copy.deepcopy(mcm.Globals.grillesAnte[-1])
			mcm.Globals.grille = self.autoCell.grille
			self.grille.update()
			mcm.Globals.etape = self.options.numEtape.value()


	# Fonction permettant une génération aléatoire globale de l'automate
	def genRandom(self) :
		self.autoCell.__init__(mcm.Globals.grilleLignes, mcm.Globals.grilleColonnes,
				       False, mcm.Globals.nombreEtats,
				       mcm.Globals.torus, mcm.Globals.tailleVoisinage, False, False,
				       False, self.options.eqDensite.isChecked(), mcm.Globals.typeVoisinage, mcm.Globals.totalistique)
		mcm.Globals.grille = self.autoCell.grille
		mcm.Globals.grillesAnte = [copy.deepcopy(mcm.Globals.grille)]
		mcm.Globals.regles = self.autoCell.regles

		for i in range(mcm.Globals.nombreEtats) :
			mcm.Globals.poidsVoisinage[i] = self.autoCell.poids[i]

		densites = self.seuilToDensite(self.autoCell.seuils)

		for i in range(5) :
			self.options.densites[i].setValue(0)
		for i in range(5) :
			self.options.densites[i].setValue(densites[i])

		self.resetEtape()
		self.resetRegles()
		self.resetVoisinage()
		self.grille.update()


	# Fonction de conversion de densité vers seuil
	def densiteToSeuil(self, densites) :
		nb = mcm.Globals.nombreEtats
		seuils = [densites[0]]

		for i in range(1, nb) :
			seuils.append(densites[i] + seuils[i-1])

		gap = 100 - seuils[nb-1]
		if gap :
			self.options.densites[0].setValue(densites[0] + gap)
			mcm.Globals.densite[0] += gap
			for i in range(nb) :
				seuils[i] += gap

		return seuils


	# Fonction de conversion de seuil vers densité
	def seuilToDensite(self, seuils) :
		nb = mcm.Globals.nombreEtats
		densites = [seuils[0]]

		for i in range(1, nb) :
			densites.append(seuils[i] - seuils[i-1])

		for i in range(nb, 5) :
			densites.append(0)

		return densites


	# Fonction de conversion de la syntaxe interne des règles vers la syntaxe utilisateur
	def reglesIntToExt(self, regles) : return (self.transRegles.intToExt(regles) if regles else "")


	# Fonction de conversion de la syntaxe utilisateur vers la syntaxe interne des règles
	def reglesExtToInt(self, regles) :
		rules = self.transRegles.extToInt(regles, mcm.Globals.nombreEtats, mcm.Globals.tailleVoisinage)
		if not rules :
			self.afficheErreur("Syntaxe incorrecte et/ou non respect de la configuration")
			rules = mcm.Globals.regles
		return rules


	# Fonction mettant à jour l'automate
	def autoCellUpdate(self) :
		self.autoCell.__init__(mcm.Globals.grilleLignes, mcm.Globals.grilleColonnes,
				       mcm.Globals.grille, mcm.Globals.nombreEtats,
				       mcm.Globals.torus, mcm.Globals.tailleVoisinage,
				       mcm.Globals.regles, mcm.Globals.poidsVoisinage,
				       self.densiteToSeuil(mcm.Globals.densite))
		if not mcm.Globals.regles :
			mcm.Globals.regles = self.autoCell.regles
			self.resetRegles()


if __name__ == '__main__' : Main()

